package com.shoulder.resourceserver.autoconfigure;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.shoulder.core.annotation.Anonymous;
import com.shoulder.core.enums.HeaderEnums;
import com.shoulder.core.filter.LauncherXssFilter;
import com.shoulder.core.properties.SecurityProperties;
import com.shoulder.core.properties.XssProperties;
import com.shoulder.core.utils.FreeUtils;
import com.shoulder.imports.Imports;
import com.shoulder.resourceserver.entrypoint.SecurityAuthenticationEntryPoint;
import com.shoulder.resourceserver.handler.PasswordEncoderHandler;
import com.shoulder.resourceserver.handler.SecurityAccessDeniedHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.List;
import java.util.Map;

@Imports(value = AutoConfiguration.class)
@AutoConfiguration
@EnableWebSecurity
@EnableConfigurationProperties({SecurityProperties.class, XssProperties.class})
@EnableMethodSecurity
public class ResourceSecurityAutoConfiguration {
    /**
     * 配置 spring security 相关的过滤器链
     */
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http
            , AccessDeniedHandler accessDeniedHandler
            , AuthenticationEntryPoint authenticationEntryPoint
            , SecurityProperties securityProperties
            , XssProperties xssProperties
            , ApplicationContext applicationContext
            , BearerTokenResolver bearerTokenResolver)
            throws Exception {

        List<String> excludeUrls = CollectionUtils.isEmpty(securityProperties.getExcludeUrls()) ? Lists.newArrayList() :
                securityProperties.getExcludeUrls();

        // 20240202: 实际在使用过程中去Nacos或者其他配置文件进行 url 的配置觉得很麻烦,于是添加了 注解方式 支持
        Map<RequestMappingInfo, HandlerMethod> methods = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();

        methods.forEach((info, method) -> {
            Anonymous anonymous = method.getMethodAnnotation(Anonymous.class);
            if (anonymous != null) {
                if (info.getPathPatternsCondition() != null) {
                    excludeUrls.addAll(info.getPathPatternsCondition().getPatternValues());
                }
            }
        });
        String[] urls = CollectionUtils.isEmpty(excludeUrls) ? FreeUtils.build() : FreeUtils.build(Iterables.toArray(excludeUrls, String.class));

        http.authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers(urls).permitAll()
                        .anyRequest().authenticated()
                )
                .csrf(AbstractHttpConfigurer::disable)
                .cors(AbstractHttpConfigurer::disable)
                // Form login handles the redirect to the login page from the
                // authorization server filter chain
                .formLogin(Customizer.withDefaults())
                .addFilterBefore(new LauncherXssFilter(xssProperties), UsernamePasswordAuthenticationFilter.class)
                //添加BearerTokenAuthenticationFilter，将认证服务当做一个资源服务，解析请求头中的token
                .oauth2ResourceServer((resourceServer) -> resourceServer
                        //自定义 Bearer Token 的请求头,区分系统的 Authorization 请求头
                        .bearerTokenResolver(bearerTokenResolver)
                        .accessDeniedHandler(accessDeniedHandler)
                        .authenticationEntryPoint(authenticationEntryPoint)
                        .jwt(Customizer.withDefaults()));
        return http.build();
    }

    /**
     * 配置PasswordEncoder
     */

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderHandler.createDelegatingPasswordEncoder();
    }

    /**
     * 自定义权限异常
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return new SecurityAccessDeniedHandler();
    }

    /**
     * 自定义认证异常
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new SecurityAuthenticationEntryPoint();
    }


    /**
     * 配置基于内存的登录用户信息
     * 实际项目中重新定义个 UserDetailsService 的 bean,这里做测试用
     */
    @Bean
    @ConditionalOnMissingBean(UserDetailsService.class)
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withUsername("abc")
                .password("abc")
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

    /**
     * 自定义Bearer请求头
     */
    @Bean
    public BearerTokenResolver bearerTokenResolver() {
        DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
        bearerTokenResolver.setBearerTokenHeaderName(HeaderEnums.BEARER_HEADER.getCode());
        return bearerTokenResolver;
    }

    //在授权服务器模块中已经定义了这个bean
    @ConditionalOnMissingBean(JwtDecoder.class)
    @ConditionalOnProperty(value = "launcher.security.jwkSetUri")
    @Bean
    public JwtDecoder jwtDecoder(SecurityProperties properties) {
        return NimbusJwtDecoder.withJwkSetUri(properties.getJwkSetUri()).build();
    }
}
